package com.example.es.restclient.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.es.restclient.mapper.HotelMapper;
import com.example.es.restclient.pojo.Hotel;
import com.example.es.restclient.pojo.HotelDoc;
import com.example.es.restclient.pojo.PageResult;
import com.example.es.restclient.pojo.RequestParams;
import com.example.es.restclient.service.HotelService;
import com.example.es.restclient.utils.StringUtils;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.elasticsearch.search.suggest.Suggest;
import org.elasticsearch.search.suggest.SuggestBuilder;
import org.elasticsearch.search.suggest.SuggestBuilders;
import org.elasticsearch.search.suggest.completion.CompletionSuggestion;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
public class HotelServiceImpl extends ServiceImpl<HotelMapper, Hotel> implements HotelService {
	@Autowired
	private RestHighLevelClient client;

	@Override
	public PageResult search(RequestParams params) {
		// 1. 准备request
		// 2.准备dsl
		// 3.发送请求 得到响应
		// 4.结果解析
		try {
			SearchRequest request = new SearchRequest("hotel");
			SearchResponse response = client.search(buildQuery(request, params), RequestOptions.DEFAULT);
			return handleResponse(response);
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	@Override
	public Map<String, List<String>> filters(RequestParams params) {
		SearchRequest request = new SearchRequest("hotel");

		buildQuery(request, params);

		request.source().size(0);
		buildAggregation(request);
		Map<String, List<String>> result = new HashMap<>();
		SearchResponse response = null;
		try {
			response = client.search(request, RequestOptions.DEFAULT);
			Aggregations aggregations = response.getAggregations();
			List<String> brandList = getAggByName(aggregations, "brandAgg");
			result.put("brand", brandList);


			List<String> cityList = getAggByName(aggregations, "cityAgg");
			result.put("city", cityList);


			List<String> starList = getAggByName(aggregations, "starAgg");
			result.put("starName", starList);
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
		return result;
	}


	private static List<String> getAggByName(Aggregations aggregations, String aggName) {
		Terms brandTerms = aggregations.get(aggName);
		List<? extends Terms.Bucket> buckets = brandTerms.getBuckets();

		List<String> brandList = new ArrayList<>();

		for (Terms.Bucket bucket : buckets) {
			brandList.add(bucket.getKeyAsString());
		}
		return brandList;
	}

	private static void buildAggregation(SearchRequest request) {
		request.source().aggregation(AggregationBuilders
				.terms("brandAgg")
				.field("brand")
				.size(10)
		);
		request.source().aggregation(AggregationBuilders
				.terms("cityAgg")
				.field("city")
				.size(10)
		);
		request.source().aggregation(AggregationBuilders
				.terms("starAgg")
				.field("starName.keyword")
				.size(10)
		);
	}

	private static SearchRequest buildQuery(SearchRequest request, RequestParams params) {

		String key = params.getKey();
		BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
		if (key == null || "".equals(key)) {
			boolQuery.must(QueryBuilders.matchAllQuery());
			// request.source().query(QueryBuilders.matchAllQuery());
		} else {
			boolQuery.must(QueryBuilders.matchQuery("all", params.getKey()));
			// request.source().query(QueryBuilders.matchQuery("all", params.getKey()));
		}
		String city = params.getCity();
		if (StringUtils.isNotBlank(city)) {
			boolQuery.filter(QueryBuilders.termQuery("city", city));
		}

		if (StringUtils.isNotBlank(params.getBrand())) {
			boolQuery.filter(QueryBuilders.termQuery("brand", params.getBrand()));
		}
		if (StringUtils.isNotBlank(params.getStarName())) {
			boolQuery.filter(QueryBuilders.termQuery("starName", params.getStarName()));
		}
		if (params.getMinPrice() != null) {
			boolQuery.filter(QueryBuilders.rangeQuery("price").gte(params.getMinPrice()));
		}
		if (params.getMinPrice() != null) {
			boolQuery.filter(QueryBuilders.rangeQuery("price").lte(params.getMaxPrice()));
		}

		FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery(
				boolQuery,
				new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
						new FunctionScoreQueryBuilder.FilterFunctionBuilder(
								QueryBuilders.termQuery("isAD", true),
								ScoreFunctionBuilders.weightFactorFunction(5)
						)
				}
		);

		request.source().query(functionScoreQueryBuilder).from((params.getPage() - 1) * params.getSize()).size(params.getSize());

		if (StringUtils.isNotBlank(params.getLocation())) {
			request.source().sort(SortBuilders.geoDistanceSort("location", new GeoPoint(params.getLocation())).order(SortOrder.ASC).unit(DistanceUnit.KILOMETERS));
		}

		return request;
	}


	private PageResult handleResponse(SearchResponse response) throws IOException {

		// System.out.println(response);
		SearchHits hits = response.getHits();
		PageResult result = new PageResult();
		long total = hits.getTotalHits().value;
		result.setTotal(total);
		List<HotelDoc> tmpList = new ArrayList<>();
		// System.out.println(total);
		SearchHit[] searchHits = hits.getHits();
		for (SearchHit searchHit : searchHits) {
			String json = searchHit.getSourceAsString();
			// Map<String, HighlightField> highlightFields = searchHit.getHighlightFields();
			// System.out.println(highlightFields);
			// System.out.println(JSON.parseObject(json, HotelDoc.class));
			HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
			Object[] sortValues = searchHit.getSortValues();
			if (sortValues != null && sortValues.length > 0) {
				hotelDoc.setDistance(sortValues[0]);
			}
			tmpList.add(hotelDoc);
		}
		result.setHotels(tmpList);
		return result;
	}


	@Override
	public List<String> getSuggestion(String key) {
		SearchRequest request = new SearchRequest("hotel");

		request.source().suggest(new SuggestBuilder()
				.addSuggestion("mySuggestion",
						SuggestBuilders.completionSuggestion("suggestion")
								.prefix(key)
								.skipDuplicates(true)
								.size(10)
				)
		);

		List<String> result = new ArrayList<>();

		SearchResponse response = null;
		try {
			response = client.search(request, RequestOptions.DEFAULT);
			// System.out.println(response);
			Suggest suggest = response.getSuggest();
			CompletionSuggestion mySuggestion = suggest.getSuggestion("mySuggestion");
			for (CompletionSuggestion.Entry.Option option : mySuggestion.getOptions()) {
				String text = option.getText().string();
				// System.out.println(text);
				result.add(text);
			}
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
		return result;
	}

	@Override
	public void insertById(Long id) {
		Hotel hotel = getById(id);
		HotelDoc hotelDoc = new HotelDoc(hotel);
		IndexRequest request = new IndexRequest("hotel").id(id.toString());

		request.source(JSON.toJSONString(hotelDoc), XContentType.JSON);
		try {
			client.index(request, RequestOptions.DEFAULT);
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	@Override
	public void deleteById(Long id) {
		DeleteRequest request = new DeleteRequest("hotel", id.toString());
		try {
			client.delete(request, RequestOptions.DEFAULT);
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}
}
